//
// Created by Administrator on 2021/9/1.
//

#ifndef OPENGLLEARN_APP_H
#define OPENGLLEARN_APP_H

#include "Application/Application.h"

#include "Geometry/Vector3.h"
#include "Geometry/Matrix4x4.h"
#include "Shader/ShaderProgram.h"
#include "Sbm/Object.h"

class App : public Application
{
protected:
    void OnCreate() override
    {
        ShaderInfo info(
                "../../Chapter13/res/phong_light_vert.glsl",
                "../../Chapter13/res/phong_light_frag.glsl");
        sp_ = ShaderProgram(info);
        sp_.Use();

        object_.Load("../../Chapter13/res/dragon.sbm");

        glCreateBuffers(4, ubo_);
        glBindBuffersBase(GL_UNIFORM_BUFFER, 0, 4, ubo_);
        glNamedBufferData(ubo_[0], sizeof(Transform), nullptr, GL_STATIC_DRAW);
        glNamedBufferData(ubo_[1], sizeof(Light), nullptr, GL_STATIC_DRAW);
        glNamedBufferData(ubo_[2], sizeof(Material), nullptr, GL_STATIC_DRAW);
        glNamedBufferData(ubo_[3], sizeof(Material), nullptr, GL_STATIC_DRAW);
        //glBufferData(GL_UNIFORM_BUFFER, sizeof(Transform) + sizeof(Light) + sizeof(Material), nullptr, GL_STATIC_DRAW);
        SetMaterialUniforms();
        SetLightUniforms();
        SetRimUniforms();

        glEnable(GL_CULL_FACE);
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_LEQUAL);
    }

    void OnRender() override
    {
        static const GLfloat bgColor[] = {0.0f, 0.0f, 0.0f, 0.0f};
        static const GLfloat depth[] = {1.0f};
        glClearBufferfv(GL_COLOR, 0, bgColor);
        glClearBufferfv(GL_DEPTH, 0, depth);

        SetTransformUniforms();
        object_.Render();
    }

    void OnDestroy() override
    {
        object_.Free();
    }

    void OnCursorPosChanged(float x, float y) override
    {

    }

    void OnKeyRepeat(int key) override
    {

    }

    void OnKeyPress(int key) override
    {

    }

private:
    void SetTransformUniforms()
    {
        auto* buffer = reinterpret_cast<Transform*>(glMapNamedBufferRange(
                ubo_[0],
                0,
                sizeof(Transform),
                GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT));
        auto translation = Matrix4x4(cosf(40.0f * DEG_2_RAD), 0.0f, sinf(40.0f * DEG_2_RAD), 0.0f,
                                     0.0f, 1.0f, 0.0f, -5.0f,
                                     -sinf(40.0f * DEG_2_RAD), 0.0f, cosf(40.0f * DEG_2_RAD), -10.0f);
        buffer->mvMatrix = (cameraMatrix * translation).Transport();
        buffer->viewMatrix = cameraMatrix.Transport();
        buffer->projMatrix = projectMatrix.Transport();
        glUnmapNamedBuffer(ubo_[0]);
    }

    void SetLightUniforms()
    {
        auto* buffer = reinterpret_cast<Light*>(glMapNamedBufferRange(
                ubo_[1],
                0,
                sizeof(Light),
                GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT));
        buffer->position = Vector4(20.0f, 20.0f, 20.0f, 1.0f);
        buffer->ambient = Vector4(0.1f, 0.1f, 0.1f, 1.0f);
        buffer->diffuse = Vector4(0.5f, 0.2f, 0.7f, 1.0f);
        buffer->specular = Vector4(1.0f, 1.0f, 1.0f, 1.0f);
        glUnmapNamedBuffer(ubo_[1]);
    }

    void SetMaterialUniforms()
    {
        //glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo_[2]);
        auto* buffer = reinterpret_cast<Material*>(glMapNamedBufferRange(
                ubo_[2],
                0,
                sizeof(Material),
                GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT));
        buffer->ambient = Vector4(1.0f, 1.0f, 1.0f, 1.0f);
        buffer->diffuse = Vector4(1.0f, 1.0f, 1.0f, 1.0f);
        buffer->specular = Vector4(1.0f, 1.0f, 1.0f, 1.0f);
        buffer->shiness = 128.0f;
        glUnmapNamedBuffer(ubo_[2]);
    }

    void SetRimUniforms()
    {
        auto* buffer = reinterpret_cast<Rim*>(glMapNamedBufferRange(
                ubo_[3],
                0,
                sizeof(Rim),
                GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT
        ));
        buffer->color = Vector4(0.8f, 0.8f, 0.8f, 1.0f);
        buffer->power = 30.0f;
        glUnmapNamedBuffer(ubo_[3]);
    }

    struct Transform
    {
        Matrix4x4 mvMatrix;
        Matrix4x4 viewMatrix;
        Matrix4x4 projMatrix;
    };

    struct Vector4
    {
        Vector4(float xx, float yy, float zz, float ww)
            : x(xx), y(yy), z(zz), w(ww) {}
        float x;
        float y;
        float z;
        float w;
    };
    struct Light
    {
        Vector4 position;
        Vector4 ambient;
        Vector4 diffuse;
        Vector4 specular;
    };
    struct Material
    {
        Vector4 ambient;
        Vector4 diffuse;
        Vector4 specular;
        float shiness;
    };
    struct Rim
    {
        Vector4 color;
        float power;
    };

    ShaderProgram sp_;
    SBM::Object object_;

    GLuint ubo_[4];
};

#endif //OPENGLLEARN_APP_H
